/******************************************************************************* * Copyright (c) 2000, 2011 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Matt Chapman, mpchapman@gmail.com - 89977 Make JDT .java agnostic *******************************************************************************/ package org.eclipse.jdt.internal.corext.refactoring.rename; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IProduct; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IResource; import org.eclipse.text.edits.ReplaceEdit; import org.eclipse.ltk.core.refactoring.Change; import org.eclipse.ltk.core.refactoring.GroupCategory; import org.eclipse.ltk.core.refactoring.GroupCategorySet; import org.eclipse.ltk.core.refactoring.IResourceMapper; import org.eclipse.ltk.core.refactoring.RefactoringDescriptor; import org.eclipse.ltk.core.refactoring.RefactoringStatus; import org.eclipse.ltk.core.refactoring.RefactoringStatusContext; import org.eclipse.ltk.core.refactoring.TextChange; import org.eclipse.ltk.core.refactoring.TextFileChange; import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext; import org.eclipse.ltk.core.refactoring.participants.IParticipantDescriptorFilter; import org.eclipse.ltk.core.refactoring.participants.RefactoringProcessor; import org.eclipse.ltk.core.refactoring.participants.RenameArguments; import org.eclipse.ltk.core.refactoring.resource.RenameResourceChange; import org.eclipse.jdt.core.Flags; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IField; import org.eclipse.jdt.core.IImportDeclaration; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.ILocalVariable; import org.eclipse.jdt.core.IMember; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IPackageFragment; import org.eclipse.jdt.core.ISourceRange; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.ITypeHierarchy; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.Signature; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.Modifier; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.refactoring.IJavaElementMapper; import org.eclipse.jdt.core.refactoring.IJavaRefactorings; import org.eclipse.jdt.core.refactoring.RenameTypeArguments; import org.eclipse.jdt.core.refactoring.descriptors.JavaRefactoringDescriptor; import org.eclipse.jdt.core.refactoring.descriptors.RenameJavaElementDescriptor; import org.eclipse.jdt.core.search.IJavaSearchConstants; import org.eclipse.jdt.core.search.IJavaSearchScope; import org.eclipse.jdt.core.search.SearchMatch; import org.eclipse.jdt.core.search.SearchPattern; import org.eclipse.jdt.core.search.TypeReferenceMatch; import org.eclipse.jdt.internal.core.refactoring.descriptors.RefactoringSignatureDescriptorFactory; import org.eclipse.jdt.internal.corext.refactoring.Checks; import org.eclipse.jdt.internal.corext.refactoring.JDTRefactoringDescriptorComment; import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringArguments; import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringDescriptorUtil; import org.eclipse.jdt.internal.corext.refactoring.RefactoringAvailabilityTester; import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages; import org.eclipse.jdt.internal.corext.refactoring.RefactoringScopeFactory; import org.eclipse.jdt.internal.corext.refactoring.RefactoringSearchEngine; import org.eclipse.jdt.internal.corext.refactoring.SearchResultGroup; import org.eclipse.jdt.internal.corext.refactoring.base.JavaStatusContext; import org.eclipse.jdt.internal.corext.refactoring.base.ReferencesInBinaryContext; import org.eclipse.jdt.internal.corext.refactoring.changes.DynamicValidationRefactoringChange; import org.eclipse.jdt.internal.corext.refactoring.changes.RenameCompilationUnitChange; import org.eclipse.jdt.internal.corext.refactoring.changes.TextChangeCompatibility; import org.eclipse.jdt.internal.corext.refactoring.participants.JavaProcessors; import org.eclipse.jdt.internal.corext.refactoring.tagging.IQualifiedNameUpdating; import org.eclipse.jdt.internal.corext.refactoring.tagging.IReferenceUpdating; import org.eclipse.jdt.internal.corext.refactoring.tagging.ISimilarDeclarationUpdating; import org.eclipse.jdt.internal.corext.refactoring.tagging.ITextUpdating; import org.eclipse.jdt.internal.corext.refactoring.util.Changes; import org.eclipse.jdt.internal.corext.refactoring.util.JavaElementUtil; import org.eclipse.jdt.internal.corext.refactoring.util.QualifiedNameFinder; import org.eclipse.jdt.internal.corext.refactoring.util.QualifiedNameSearchResult; import org.eclipse.jdt.internal.corext.refactoring.util.RefactoringASTParser; import org.eclipse.jdt.internal.corext.refactoring.util.ResourceUtil; import org.eclipse.jdt.internal.corext.refactoring.util.TextChangeManager; import org.eclipse.jdt.internal.corext.util.JavaModelUtil; import org.eclipse.jdt.internal.corext.util.JdtFlags; import org.eclipse.jdt.internal.corext.util.Messages; import org.eclipse.jdt.internal.corext.util.SearchUtils; import org.eclipse.jdt.ui.JavaElementLabels; import org.eclipse.jdt.ui.refactoring.IRefactoringProcessorIds; import org.eclipse.jdt.ui.refactoring.RefactoringSaveHelper; import org.eclipse.jdt.internal.ui.JavaPlugin; import org.eclipse.jdt.internal.ui.javaeditor.ASTProvider; import org.eclipse.jdt.internal.ui.viewsupport.BasicElementLabels; /** * * @author Mohsen Vakilian, nchen - Made the class comply to the API of watched processors. * */ public class RenameTypeProcessor extends JavaRenameProcessor implements ITextUpdating, IReferenceUpdating, IQualifiedNameUpdating, ISimilarDeclarationUpdating, IResourceMapper, IJavaElementMapper { private static final String ATTRIBUTE_QUALIFIED= "qualified"; //$NON-NLS-1$ private static final String ATTRIBUTE_TEXTUAL_MATCHES= "textual"; //$NON-NLS-1$ private static final String ATTRIBUTE_PATTERNS= "patterns"; //$NON-NLS-1$ private static final String ATTRIBUTE_SIMILAR_DECLARATIONS= "similarDeclarations"; //$NON-NLS-1$ private static final String ATTRIBUTE_MATCHING_STRATEGY= "matchStrategy"; //$NON-NLS-1$ private static final GroupCategorySet CATEGORY_TYPE_RENAME= new GroupCategorySet( new GroupCategory( "org.eclipse.jdt.internal.corext.refactoring.rename.renameType.type", RefactoringCoreMessages.RenameTypeProcessor_changeCategory_type, RefactoringCoreMessages.RenameTypeProcessor_changeCategory_type_description)); //$NON-NLS-1$ private static final GroupCategorySet CATEGORY_METHOD_RENAME= new GroupCategorySet( new GroupCategory( "org.eclipse.jdt.internal.corext.refactoring.rename.renameType.method", RefactoringCoreMessages.RenameTypeProcessor_changeCategory_method, RefactoringCoreMessages.RenameTypeProcessor_changeCategory_method_description)); //$NON-NLS-1$ private static final GroupCategorySet CATEGORY_FIELD_RENAME= new GroupCategorySet( new GroupCategory( "org.eclipse.jdt.internal.corext.refactoring.rename.renameType.field", RefactoringCoreMessages.RenameTypeProcessor_changeCategory_fields, RefactoringCoreMessages.RenameTypeProcessor_changeCategory_fields_description)); //$NON-NLS-1$ private static final GroupCategorySet CATEGORY_LOCAL_RENAME= new GroupCategorySet( new GroupCategory( "org.eclipse.jdt.internal.corext.refactoring.rename.renameType.local", RefactoringCoreMessages.RenameTypeProcessor_changeCategory_local_variables, RefactoringCoreMessages.RenameTypeProcessor_changeCategory_local_variables_description)); //$NON-NLS-1$ private IType fType; private SearchResultGroup[] fReferences; private TextChangeManager fChangeManager; private QualifiedNameSearchResult fQualifiedNameSearchResult; private boolean fUpdateReferences; private boolean fUpdateTextualMatches; private boolean fUpdateQualifiedNames; private String fFilePatterns; // --- similar elements private boolean fUpdateSimilarElements; private Map<IJavaElement, String> fFinalSimilarElementToName= null; private int fRenamingStrategy; // Preloaded information for the UI. private LinkedHashMap<IJavaElement, String> fPreloadedElementToName= null; private Map<IJavaElement, Boolean> fPreloadedElementToSelection= null; private LinkedHashMap<IJavaElement, String> fPreloadedElementToNameDefault= null; // Cache information to decide whether to // re-update references and preload info private String fCachedNewName= null; private boolean fCachedRenameSimilarElements= false; private int fCachedRenamingStrategy= -1; private RefactoringStatus fCachedRefactoringStatus= null; public static final class ParticipantDescriptorFilter implements IParticipantDescriptorFilter { public boolean select(IConfigurationElement element, RefactoringStatus status) { IConfigurationElement[] params= element.getChildren(PARAM); for (int i= 0; i < params.length; i++) { IConfigurationElement param= params[i]; if ("handlesSimilarDeclarations".equals(param.getAttribute(NAME)) && //$NON-NLS-1$ "false".equals(param.getAttribute(VALUE))) { //$NON-NLS-1$ return false; } } return true; } } private class NoOverrideProgressMonitor extends SubProgressMonitor { public NoOverrideProgressMonitor(IProgressMonitor monitor, int ticks) { super(monitor, ticks, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL); } @Override public void setTaskName(String name) { // do nothing } } /** * Creates a new rename type processor. * * @param type the type, or <code>null</code> if invoked by scripting */ public RenameTypeProcessor(IType type) { fType= type; if (type != null) setNewElementName(type.getElementName()); fUpdateReferences= true; //default is yes fUpdateTextualMatches= false; fUpdateSimilarElements= false; // default is no fRenamingStrategy= RenamingNameSuggestor.STRATEGY_EXACT; } public RenameTypeProcessor(JavaRefactoringArguments arguments, RefactoringStatus status) { this(null); status.merge(initialize(arguments)); } public IType getType() { return fType; } @Override public String getIdentifier() { return IRefactoringProcessorIds.RENAME_TYPE_PROCESSOR; } @Override public boolean isApplicable() throws CoreException { return RefactoringAvailabilityTester.isRenameAvailable(fType); } @Override public String getProcessorName() { return RefactoringCoreMessages.RenameTypeRefactoring_name; } @Override protected String[] getAffectedProjectNatures() throws CoreException { return JavaProcessors.computeAffectedNatures(fType); } @Override public Object[] getElements() { return new Object[] { fType }; } @Override protected RenameModifications computeRenameModifications() { RenameModifications result= new RenameModifications(); result.rename(fType, new RenameTypeArguments(getNewElementName(), getUpdateReferences(), getUpdateSimilarDeclarations(), getSimilarElements()), createParticipantDescriptorFilter()); if (isPrimaryType()) { ICompilationUnit cu= fType.getCompilationUnit(); String newCUName= getNewCompilationUnit().getElementName(); result.rename(cu, new RenameArguments(newCUName, getUpdateReferences())); } return result; } /* * Note: this is a handle-only method! */ private boolean isPrimaryType() { String cuName= fType.getCompilationUnit().getElementName(); String typeName= fType.getElementName(); return Checks.isTopLevel(fType) && JavaCore.removeJavaLikeExtension(cuName).equals(typeName); } //---- IRenameProcessor ---------------------------------------------- public String getCurrentElementName() { return fType.getElementName(); } public String getCurrentElementQualifier() { return JavaModelUtil.getTypeContainerName(fType); } public RefactoringStatus checkNewElementName(String newName) { Assert.isNotNull(newName, "new name"); //$NON-NLS-1$ RefactoringStatus result= Checks.checkTypeName(newName, fType); if (Checks.isAlreadyNamed(fType, newName)) result.addFatalError(RefactoringCoreMessages.RenameTypeRefactoring_choose_another_name); return result; } public Object getNewElement() { if (Checks.isTopLevel(fType)) { return getNewCompilationUnit().getType(getNewElementName()); } else { return fType.getDeclaringType().getType(getNewElementName()); } } private ICompilationUnit getNewCompilationUnit() { ICompilationUnit cu= fType.getCompilationUnit(); if (isPrimaryType()) { IPackageFragment parent= fType.getPackageFragment(); String renamedCUName= JavaModelUtil.getRenamedCUName(cu, getNewElementName()); return parent.getCompilationUnit(renamedCUName); } else { return cu; } } //---- JavaRenameProcessor ------------------------------------------- protected RenameArguments createRenameArguments() { return new RenameTypeArguments(getNewElementName(), getUpdateReferences(), getUpdateSimilarDeclarations(), getSimilarElements()); } protected IParticipantDescriptorFilter createParticipantDescriptorFilter() { if (!getUpdateSimilarDeclarations()) return null; return new ParticipantDescriptorFilter(); } @Override protected IFile[] getChangedFiles() throws CoreException { List<IFile> result= new ArrayList<IFile>(); result.addAll(Arrays.asList(ResourceUtil.getFiles(fChangeManager.getAllCompilationUnits()))); if (fQualifiedNameSearchResult != null) result.addAll(Arrays.asList(fQualifiedNameSearchResult.getAllFiles())); if (willRenameCU()) result.add(ResourceUtil.getFile(fType.getCompilationUnit())); return result.toArray(new IFile[result.size()]); } @Override public int getSaveMode() { return RefactoringSaveHelper.SAVE_REFACTORING; } //---- ITextUpdating ------------------------------------------------- public boolean canEnableTextUpdating() { return true; } public boolean getUpdateTextualMatches() { return fUpdateTextualMatches; } public void setUpdateTextualMatches(boolean update) { fUpdateTextualMatches= update; } //---- IReferenceUpdating -------------------------------------- public void setUpdateReferences(boolean update) { fUpdateReferences= update; } public boolean getUpdateReferences() { return fUpdateReferences; } //---- IQualifiedNameUpdating ---------------------------------- public boolean canEnableQualifiedNameUpdating() { return !fType.getPackageFragment().isDefaultPackage() && !(fType.getParent() instanceof IType); } public boolean getUpdateQualifiedNames() { return fUpdateQualifiedNames; } public void setUpdateQualifiedNames(boolean update) { fUpdateQualifiedNames= update; } public String getFilePatterns() { return fFilePatterns; } public void setFilePatterns(String patterns) { Assert.isNotNull(patterns); fFilePatterns= patterns; } // ---- ISimilarDeclarationUpdating public boolean canEnableSimilarDeclarationUpdating() { IProduct product= Platform.getProduct(); if (product != null) { String property= product.getProperty("org.eclipse.jdt.ui.refactoring.handlesSimilarDeclarations"); //$NON-NLS-1$ if ("false".equalsIgnoreCase(property)) //$NON-NLS-1$ return false; } return true; } public void setUpdateSimilarDeclarations(boolean update) { fUpdateSimilarElements= update; } public boolean getUpdateSimilarDeclarations() { return fUpdateSimilarElements; } public int getMatchStrategy() { return fRenamingStrategy; } public void setMatchStrategy(int selectedStrategy) { fRenamingStrategy= selectedStrategy; } /** * @return the similar elements of the type, i.e. IFields, IMethods, and ILocalVariables. * Returns <code>null</code> iff similar declaration updating is not requested. */ public IJavaElement[] getSimilarElements() { if (fFinalSimilarElementToName == null) return null; Set<IJavaElement> keys= fFinalSimilarElementToName.keySet(); return keys.toArray(new IJavaElement[keys.size()]); } /** * {@inheritDoc} */ public IResource getRefactoredResource(IResource element) { if (element instanceof IFile) { if (Checks.isTopLevel(fType) && element.equals(fType.getResource())) return getNewCompilationUnit().getResource(); } return element; } /** * {@inheritDoc} */ public IJavaElement getRefactoredJavaElement(IJavaElement element) { if (element instanceof ICompilationUnit) { if (Checks.isTopLevel(fType) && element.equals(fType.getCompilationUnit())) return getNewCompilationUnit(); } else if (element instanceof IMember) { final IType newType= (IType) getNewElement(); final RefactoringHandleTransplanter transplanter= new RefactoringHandleTransplanter(fType, newType, fFinalSimilarElementToName); return transplanter.transplantHandle((IMember) element); } return element; } @Override public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException { IType primary= (IType) fType.getPrimaryElement(); if (primary == null || !primary.exists()) { String qualifiedTypeName= JavaElementLabels.getElementLabel(fType, JavaElementLabels.F_FULLY_QUALIFIED); String message= Messages.format(RefactoringCoreMessages.RenameTypeRefactoring_does_not_exist, new String[] { BasicElementLabels.getJavaElementName(qualifiedTypeName), BasicElementLabels.getFileName(fType.getCompilationUnit()) }); return RefactoringStatus.createFatalErrorStatus(message); } fType= primary; return Checks.checkIfCuBroken(fType); } @Override protected RefactoringStatus doCheckFinalConditions(IProgressMonitor pm, CheckConditionsContext context) throws CoreException { Assert.isNotNull(fType, "type"); //$NON-NLS-1$ Assert.isNotNull(getNewElementName(), "newName"); //$NON-NLS-1$ RefactoringStatus result= new RefactoringStatus(); int referenceSearchTicks= fUpdateReferences || fUpdateSimilarElements ? 15 : 0; int affectedCusTicks= fUpdateReferences || fUpdateSimilarElements ? 10 : 1; int similarElementTicks= fUpdateSimilarElements ? 85 : 0; int createChangeTicks= 5; int qualifiedNamesTicks= fUpdateQualifiedNames ? 50 : 0; try { pm.beginTask("", 12 + referenceSearchTicks + affectedCusTicks + similarElementTicks + createChangeTicks + qualifiedNamesTicks); //$NON-NLS-1$ pm.setTaskName(RefactoringCoreMessages.RenameTypeRefactoring_checking); fChangeManager= new TextChangeManager(true); result.merge(checkNewElementName(getNewElementName())); if (result.hasFatalError()) return result; result.merge(Checks.checkIfCuBroken(fType)); if (result.hasFatalError()) return result; pm.worked(1); result.merge(checkTypesInCompilationUnit()); pm.worked(1); result.merge(checkForMethodsWithConstructorNames()); pm.worked(1); result.merge(checkImportedTypes()); pm.worked(1); if (Checks.isTopLevel(fType) && (JdtFlags.isPublic(fType))) result.merge(Checks.checkCompilationUnitNewName(fType.getCompilationUnit(), getNewElementName())); pm.worked(1); if (isPrimaryType()) result.merge(checkNewPathValidity()); pm.worked(1); result.merge(checkEnclosingTypes()); pm.worked(1); result.merge(checkEnclosedTypes()); pm.worked(1); result.merge(checkTypesInPackage()); pm.worked(1); result.merge(checkTypesImportedInCu()); pm.worked(1); result.merge(Checks.checkForMainAndNativeMethods(fType)); pm.worked(1); // before doing any expensive analysis if (result.hasFatalError()) return result; result.merge(analyseEnclosedTypes()); pm.worked(1); // before doing _the really_ expensive analysis if (result.hasFatalError()) return result; // Load references, including similarly named elements if (fUpdateReferences || fUpdateSimilarElements) { pm.setTaskName(RefactoringCoreMessages.RenameTypeRefactoring_searching); result.merge(initializeReferences(new SubProgressMonitor(pm, referenceSearchTicks))); } else { fReferences= new SearchResultGroup[0]; } pm.setTaskName(RefactoringCoreMessages.RenameTypeRefactoring_checking); if (pm.isCanceled()) throw new OperationCanceledException(); if (fUpdateReferences || fUpdateSimilarElements) { result.merge(analyzeAffectedCompilationUnits(new SubProgressMonitor(pm, affectedCusTicks))); } else { Checks.checkCompileErrorsInAffectedFile(result, fType.getResource()); pm.worked(affectedCusTicks); } if (result.hasFatalError()) return result; if (fUpdateSimilarElements) { result.merge(initializeSimilarElementsRenameProcessors(new SubProgressMonitor(pm, similarElementTicks), context)); if (result.hasFatalError()) return result; } createChanges(new SubProgressMonitor(pm, createChangeTicks)); if (fUpdateQualifiedNames) computeQualifiedNameMatches(new SubProgressMonitor(pm, qualifiedNamesTicks)); return result; } finally { pm.done(); } } /** * Initializes the references to the type and the similarly named elements. This method creates * both the fReferences and the fPreloadedElementToName fields. * * May be called from the UI. * * @param monitor progress monitor * @return initialization status * @throws JavaModelException some fundamental error with the underlying model * @throws OperationCanceledException if user canceled the task * */ public RefactoringStatus initializeReferences(IProgressMonitor monitor) throws JavaModelException, OperationCanceledException { Assert.isNotNull(fType); Assert.isNotNull(getNewElementName()); // Do not search again if the preconditions have not changed. // Search depends on the type, the new name, the similarly named elements, and // the strategy if (fReferences != null && (getNewElementName().equals(fCachedNewName)) && (fCachedRenameSimilarElements == getUpdateSimilarDeclarations()) && (fCachedRenamingStrategy == fRenamingStrategy)) return fCachedRefactoringStatus; fCachedNewName= getNewElementName(); fCachedRenameSimilarElements= fUpdateSimilarElements; fCachedRenamingStrategy= fRenamingStrategy; fCachedRefactoringStatus= new RefactoringStatus(); try { SearchPattern pattern= SearchPattern.createPattern(fType, IJavaSearchConstants.REFERENCES, SearchUtils.GENERICS_AGNOSTIC_MATCH_RULE); String binaryRefsDescription= Messages.format(RefactoringCoreMessages.ReferencesInBinaryContext_ref_in_binaries_description, BasicElementLabels.getJavaElementName(fType.getElementName())); ReferencesInBinaryContext binaryRefs= new ReferencesInBinaryContext(binaryRefsDescription); fReferences= RefactoringSearchEngine.search( pattern, RefactoringScopeFactory.create(fType, true, false), new TypeOccurrenceCollector(fType, binaryRefs), monitor, fCachedRefactoringStatus); binaryRefs.addErrorIfNecessary(fCachedRefactoringStatus); fReferences= Checks.excludeCompilationUnits(fReferences, fCachedRefactoringStatus); fPreloadedElementToName= new LinkedHashMap<IJavaElement, String>(); fPreloadedElementToSelection= new HashMap<IJavaElement, Boolean>(); final String unQualifiedTypeName= fType.getElementName(); monitor.beginTask("", fReferences.length); //$NON-NLS-1$ if (getUpdateSimilarDeclarations()) { RenamingNameSuggestor sugg= new RenamingNameSuggestor(fRenamingStrategy); for (int i= 0; i < fReferences.length; i++) { final ICompilationUnit cu= fReferences[i].getCompilationUnit(); if (cu == null) continue; final SearchMatch[] results= fReferences[i].getSearchResults(); for (int j= 0; j < results.length; j++) { if (!(results[j] instanceof TypeReferenceMatch)) continue; final TypeReferenceMatch match= (TypeReferenceMatch) results[j]; final List<IJavaElement> matches= new ArrayList<IJavaElement>(); if (match.getLocalElement() != null) { if (match.getLocalElement() instanceof ILocalVariable) { matches.add(match.getLocalElement()); } // else don't update (e.g. match in type parameter, annotation, ...) } else { matches.add((IJavaElement) match.getElement()); } final IJavaElement[] others= match.getOtherElements(); if (others != null) matches.addAll(Arrays.asList(others)); for (Iterator<IJavaElement> iter= matches.iterator(); iter.hasNext();) { final IJavaElement element= iter.next(); if (!(element instanceof IMethod) && !(element instanceof IField) && !(element instanceof ILocalVariable)) continue; if (!isInDeclaredType(match.getOffset(), element)) continue; if (element instanceof IField) { final IField currentField= (IField) element; final String newFieldName= sugg.suggestNewFieldName(currentField.getJavaProject(), currentField.getElementName(), Flags.isStatic(currentField.getFlags()), unQualifiedTypeName, getNewElementName()); if (newFieldName != null) fPreloadedElementToName.put(currentField, newFieldName); } else if (element instanceof IMethod) { final IMethod currentMethod= (IMethod) element; addMethodRename(unQualifiedTypeName, sugg, currentMethod); } else if (element instanceof ILocalVariable) { final ILocalVariable currentLocal= (ILocalVariable) element; final boolean isParameter; if (currentLocal.isParameter()) { addMethodRename(unQualifiedTypeName, sugg, (IMethod) currentLocal.getParent()); isParameter= true; } else isParameter= false; final String newLocalName= sugg .suggestNewLocalName(currentLocal.getJavaProject(), currentLocal.getElementName(), isParameter, unQualifiedTypeName, getNewElementName()); if (newLocalName != null) fPreloadedElementToName.put(currentLocal, newLocalName); } } } if (monitor.isCanceled()) throw new OperationCanceledException(); } } for (Iterator<IJavaElement> iter= fPreloadedElementToName.keySet().iterator(); iter.hasNext();) { IJavaElement element= iter.next(); fPreloadedElementToSelection.put(element, Boolean.TRUE); } fPreloadedElementToNameDefault= (LinkedHashMap<IJavaElement, String>) fPreloadedElementToName.clone(); } catch (OperationCanceledException e) { fReferences= null; fPreloadedElementToName= null; throw new OperationCanceledException(); } return fCachedRefactoringStatus; } /** * @param matchOffset offset of the match * @param parentElement parent element of the match * @return true iff the given search match offset (must be a match of a type reference) lies * before the element name of its enclosing java element, false if not. In other words: * If this method returns true, the match is the declared type (or return type) of the * enclosing element. * @throws JavaModelException should not happen * */ private boolean isInDeclaredType(int matchOffset, IJavaElement parentElement) throws JavaModelException { if (parentElement != null) { int enclosingNameOffset= 0; if (parentElement instanceof IMethod || parentElement instanceof IField) enclosingNameOffset= ((IMember) parentElement).getNameRange().getOffset(); else if (parentElement instanceof ILocalVariable) enclosingNameOffset= ((ILocalVariable) parentElement).getNameRange().getOffset(); return (matchOffset < enclosingNameOffset); } return false; } private void addMethodRename(final String unQualifiedTypeName, RenamingNameSuggestor sugg, final IMethod currentMethod) throws JavaModelException { if (!currentMethod.isConstructor()) { final String newMethodName= sugg.suggestNewMethodName(currentMethod.getElementName(), unQualifiedTypeName, getNewElementName()); if (newMethodName != null) fPreloadedElementToName.put(currentMethod, newMethodName); } } private RefactoringStatus checkNewPathValidity() { IContainer c= fType.getCompilationUnit().getResource().getParent(); String notRename= RefactoringCoreMessages.RenameTypeRefactoring_will_not_rename; IStatus status= c.getWorkspace().validateName(getNewElementName(), IResource.FILE); if (status.getSeverity() == IStatus.ERROR) return RefactoringStatus.createWarningStatus(status.getMessage() + ". " + notRename); //$NON-NLS-1$ status= c.getWorkspace().validatePath(createNewPath(getNewElementName()), IResource.FILE); if (status.getSeverity() == IStatus.ERROR) return RefactoringStatus.createWarningStatus(status.getMessage() + ". " + notRename); //$NON-NLS-1$ return new RefactoringStatus(); } private String createNewPath(String newName) { return fType.getCompilationUnit().getResource().getFullPath().removeLastSegments(1).append(newName).toString(); } private RefactoringStatus checkTypesImportedInCu() throws CoreException { IImportDeclaration imp= getImportedType(fType.getCompilationUnit(), getNewElementName()); if (imp == null) return null; String msg= Messages.format(RefactoringCoreMessages.RenameTypeRefactoring_imported, new Object[] { getNewElementLabel(), BasicElementLabels.getPathLabel(fType.getCompilationUnit().getResource().getFullPath(), false) }); IJavaElement grandParent= imp.getParent().getParent(); if (grandParent instanceof ICompilationUnit) return RefactoringStatus.createErrorStatus(msg, JavaStatusContext.create(imp)); return null; } private RefactoringStatus checkTypesInPackage() throws CoreException { IType type= Checks.findTypeInPackage(fType.getPackageFragment(), getNewElementName()); if (type == null || !type.exists()) return null; String msg= Messages.format(RefactoringCoreMessages.RenameTypeRefactoring_exists, new String[] { getNewElementLabel(), JavaElementLabels.getElementLabel(fType.getPackageFragment(), JavaElementLabels.ALL_DEFAULT) }); return RefactoringStatus.createErrorStatus(msg, JavaStatusContext.create(type)); } private RefactoringStatus checkEnclosedTypes() throws CoreException { IType enclosedType= findEnclosedType(fType, getNewElementName()); if (enclosedType == null) return null; String msg= Messages.format(RefactoringCoreMessages.RenameTypeRefactoring_encloses, new String[] { BasicElementLabels.getJavaElementName(fType.getFullyQualifiedName('.')), getNewElementLabel() }); return RefactoringStatus.createErrorStatus(msg, JavaStatusContext.create(enclosedType)); } private RefactoringStatus checkEnclosingTypes() { IType enclosingType= findEnclosingType(fType, getNewElementName()); if (enclosingType == null) return null; String msg= Messages.format(RefactoringCoreMessages.RenameTypeRefactoring_enclosed, new String[] { BasicElementLabels.getJavaElementName(fType.getFullyQualifiedName('.')), getNewElementLabel() }); return RefactoringStatus.createErrorStatus(msg, JavaStatusContext.create(enclosingType)); } private String getNewElementLabel() { return BasicElementLabels.getJavaElementName(getNewElementName()); } private static IType findEnclosedType(IType type, String newName) throws CoreException { IType[] enclosedTypes= type.getTypes(); for (int i= 0; i < enclosedTypes.length; i++) { if (newName.equals(enclosedTypes[i].getElementName()) || findEnclosedType(enclosedTypes[i], newName) != null) return enclosedTypes[i]; } return null; } private static IType findEnclosingType(IType type, String newName) { IType enclosing= type.getDeclaringType(); while (enclosing != null) { if (newName.equals(enclosing.getElementName())) return enclosing; else enclosing= enclosing.getDeclaringType(); } return null; } private static IImportDeclaration getImportedType(ICompilationUnit cu, String typeName) throws CoreException { IImportDeclaration[] imports= cu.getImports(); String dotTypeName= "." + typeName; //$NON-NLS-1$ for (int i= 0; i < imports.length; i++) { if (imports[i].getElementName().endsWith(dotTypeName)) return imports[i]; } return null; } private RefactoringStatus checkForMethodsWithConstructorNames() throws CoreException { IMethod[] methods= fType.getMethods(); for (int i= 0; i < methods.length; i++) { if (methods[i].isConstructor()) continue; RefactoringStatus check= Checks.checkIfConstructorName(methods[i], methods[i].getElementName(), getNewElementName()); if (check != null) return check; } return null; } private RefactoringStatus checkImportedTypes() throws CoreException { RefactoringStatus result= new RefactoringStatus(); IImportDeclaration[] imports= fType.getCompilationUnit().getImports(); for (int i= 0; i < imports.length; i++) analyzeImportDeclaration(imports[i], result); return result; } private RefactoringStatus checkTypesInCompilationUnit() { RefactoringStatus result= new RefactoringStatus(); if (!Checks.isTopLevel(fType)) { //the other case checked in checkTypesInPackage IType siblingType= fType.getDeclaringType().getType(getNewElementName()); if (siblingType.exists()) { String msg= Messages.format(RefactoringCoreMessages.RenameTypeRefactoring_member_type_exists, new String[] { getNewElementLabel(), BasicElementLabels.getJavaElementName(fType.getDeclaringType().getFullyQualifiedName('.')) }); result.addError(msg, JavaStatusContext.create(siblingType)); } } return result; } private RefactoringStatus analyseEnclosedTypes() throws CoreException { final ISourceRange typeRange= fType.getSourceRange(); final RefactoringStatus result= new RefactoringStatus(); CompilationUnit cuNode= new RefactoringASTParser(ASTProvider.SHARED_AST_LEVEL).parse(fType.getCompilationUnit(), false); cuNode.accept(new ASTVisitor() { @Override public boolean visit(TypeDeclaration node) { // enums and annotations can't be local if (node.getStartPosition() <= typeRange.getOffset()) return true; if (node.getStartPosition() > typeRange.getOffset() + typeRange.getLength()) return true; if (getNewElementName().equals(node.getName().getIdentifier())) { RefactoringStatusContext context= JavaStatusContext.create(fType.getCompilationUnit(), node); String msg= null; if (node.isLocalTypeDeclaration()) { msg= Messages.format(RefactoringCoreMessages.RenameTypeRefactoring_local_type, new String[] { JavaElementUtil.createSignature(fType), getNewElementLabel() }); } else if (node.isMemberTypeDeclaration()) { msg= Messages.format(RefactoringCoreMessages.RenameTypeRefactoring_member_type, new String[] { JavaElementUtil.createSignature(fType), getNewElementLabel() }); } if (msg != null) result.addError(msg, context); } MethodDeclaration[] methods= node.getMethods(); for (int i= 0; i < methods.length; i++) { if (Modifier.isNative(methods[i].getModifiers())) { RefactoringStatusContext context= JavaStatusContext.create(fType.getCompilationUnit(), methods[i]); String msg= Messages.format(RefactoringCoreMessages.RenameTypeRefactoring_enclosed_type_native, BasicElementLabels.getJavaElementName(node.getName().getIdentifier())); result.addWarning(msg, context); } } return true; } }); return result; } private static ICompilationUnit getCompilationUnit(IImportDeclaration imp) { return (ICompilationUnit) imp.getParent().getParent(); } private void analyzeImportedTypes(IType[] types, RefactoringStatus result, IImportDeclaration imp) throws CoreException { for (int i= 0; i < types.length; i++) { //could this be a problem (same package imports)? if (JdtFlags.isPublic(types[i]) && types[i].getElementName().equals(getNewElementName())) { String msg= Messages.format( RefactoringCoreMessages.RenameTypeRefactoring_name_conflict1, new Object[] { JavaElementLabels.getElementLabel(types[i], JavaElementLabels.ALL_FULLY_QUALIFIED), BasicElementLabels.getPathLabel(getCompilationUnit(imp).getPath(), false) }); result.addError(msg, JavaStatusContext.create(imp)); } } } private static IJavaElement convertFromImportDeclaration(IImportDeclaration declaration) throws CoreException { if (declaration.isOnDemand()) { String packageName= declaration.getElementName().substring(0, declaration.getElementName().length() - 2); return JavaModelUtil.findTypeContainer(declaration.getJavaProject(), packageName); } else return JavaModelUtil.findTypeContainer(declaration.getJavaProject(), declaration.getElementName()); } private void analyzeImportDeclaration(IImportDeclaration imp, RefactoringStatus result) throws CoreException { if (!imp.isOnDemand()) return; //analyzed earlier IJavaElement imported= convertFromImportDeclaration(imp); if (imported == null) return; if (imported instanceof IPackageFragment) { ICompilationUnit[] cus= ((IPackageFragment) imported).getCompilationUnits(); for (int i= 0; i < cus.length; i++) { analyzeImportedTypes(cus[i].getTypes(), result, imp); } } else { //cast safe: see JavaModelUtility.convertFromImportDeclaration analyzeImportedTypes(((IType) imported).getTypes(), result, imp); } } /* * Analyzes all compilation units in which type is referenced */ private RefactoringStatus analyzeAffectedCompilationUnits(IProgressMonitor pm) throws CoreException { RefactoringStatus result= new RefactoringStatus(); result.merge(Checks.checkCompileErrorsInAffectedFiles(fReferences, fType.getResource())); pm.beginTask("", fReferences.length); //$NON-NLS-1$ result.merge(checkConflictingTypes(pm)); return result; } private RefactoringStatus checkConflictingTypes(IProgressMonitor pm) throws CoreException { RefactoringStatus result= new RefactoringStatus(); IJavaSearchScope scope= RefactoringScopeFactory.create(fType); SearchPattern pattern= SearchPattern.createPattern(getNewElementName(), IJavaSearchConstants.TYPE, IJavaSearchConstants.ALL_OCCURRENCES, SearchUtils.GENERICS_AGNOSTIC_MATCH_RULE); ICompilationUnit[] cusWithReferencesToConflictingTypes= RefactoringSearchEngine.findAffectedCompilationUnits(pattern, scope, pm, result); if (cusWithReferencesToConflictingTypes.length == 0) return result; ICompilationUnit[] cusWithReferencesToRenamedType= getCus(fReferences); ICompilationUnit[] intersection= isIntersectionEmpty(cusWithReferencesToRenamedType, cusWithReferencesToConflictingTypes); if (intersection.length == 0) return result; for (int i= 0; i < intersection.length; i++) { RefactoringStatusContext context= JavaStatusContext.create(intersection[i]); String message= Messages.format(RefactoringCoreMessages.RenameTypeRefactoring_another_type, new String[] { getNewElementLabel(), BasicElementLabels.getFileName(intersection[i]) }); result.addError(message, context); } return result; } private static ICompilationUnit[] isIntersectionEmpty(ICompilationUnit[] a1, ICompilationUnit[] a2) { Set<ICompilationUnit> set1= new HashSet<ICompilationUnit>(Arrays.asList(a1)); Set<ICompilationUnit> set2= new HashSet<ICompilationUnit>(Arrays.asList(a2)); set1.retainAll(set2); return set1.toArray(new ICompilationUnit[set1.size()]); } private static ICompilationUnit[] getCus(SearchResultGroup[] searchResultGroups) { List<ICompilationUnit> cus= new ArrayList<ICompilationUnit>(searchResultGroups.length); for (int i= 0; i < searchResultGroups.length; i++) { ICompilationUnit cu= searchResultGroups[i].getCompilationUnit(); if (cu != null) cus.add(cu); } return cus.toArray(new ICompilationUnit[cus.size()]); } //CODINGSPECTATOR: Extracted createRefactoringDescriptor @Override public Change createChange(IProgressMonitor monitor) throws CoreException { try { monitor.beginTask(RefactoringCoreMessages.RenameTypeRefactoring_creating_change, 4); final DynamicValidationRefactoringChange result= new DynamicValidationRefactoringChange(createRefactoringDescriptor(), RefactoringCoreMessages.RenameTypeProcessor_change_name); if (fChangeManager.containsChangesIn(fType.getCompilationUnit())) { TextChange textChange= fChangeManager.get(fType.getCompilationUnit()); if (textChange instanceof TextFileChange) { ((TextFileChange) textChange).setSaveMode(TextFileChange.FORCE_SAVE); } } result.addAll(fChangeManager.getAllChanges()); if (willRenameCU()) { IResource resource= fType.getCompilationUnit().getResource(); if (resource != null && resource.isLinked()) { String ext= resource.getFileExtension(); String renamedResourceName; if (ext == null) renamedResourceName= getNewElementName(); else renamedResourceName= getNewElementName() + '.' + ext; result.add(new RenameResourceChange(fType.getCompilationUnit().getPath(), renamedResourceName)); } else { String renamedCUName= JavaModelUtil.getRenamedCUName(fType.getCompilationUnit(), getNewElementName()); result.add(new RenameCompilationUnitChange(fType.getCompilationUnit(), renamedCUName)); } } monitor.worked(1); return result; } finally { fChangeManager= null; } } //CODINGSPECTATOR: Extracted from createChange private RenameJavaElementDescriptor createRefactoringDescriptor() { String project= null; IJavaProject javaProject= fType.getJavaProject(); if (javaProject != null) project= javaProject.getElementName(); int flags= JavaRefactoringDescriptor.JAR_MIGRATION | JavaRefactoringDescriptor.JAR_REFACTORING | RefactoringDescriptor.STRUCTURAL_CHANGE; try { if (!Flags.isPrivate(fType.getFlags())) flags|= RefactoringDescriptor.MULTI_CHANGE; if (fType.isAnonymous() || fType.isLocal()) flags|= JavaRefactoringDescriptor.JAR_SOURCE_ATTACHMENT; } catch (JavaModelException exception) { JavaPlugin.log(exception); } final String description= Messages.format(RefactoringCoreMessages.RenameTypeProcessor_descriptor_description_short, BasicElementLabels.getJavaElementName(fType.getElementName())); final String header= Messages.format(RefactoringCoreMessages.RenameTypeProcessor_descriptor_description, new String[] { JavaElementLabels.getElementLabel(fType, JavaElementLabels.ALL_FULLY_QUALIFIED), getNewElementLabel() }); final String comment= new JDTRefactoringDescriptorComment(project, this, header).asString(); final RenameJavaElementDescriptor descriptor= RefactoringSignatureDescriptorFactory.createRenameJavaElementDescriptor(IJavaRefactorings.RENAME_TYPE); descriptor.setProject(project); descriptor.setDescription(description); descriptor.setComment(comment); descriptor.setFlags(flags); descriptor.setJavaElement(fType); descriptor.setNewName(getNewElementName()); descriptor.setUpdateQualifiedNames(fUpdateQualifiedNames); descriptor.setUpdateTextualOccurrences(fUpdateTextualMatches); descriptor.setUpdateReferences(fUpdateReferences); if (fUpdateQualifiedNames && fFilePatterns != null && !"".equals(fFilePatterns)) //$NON-NLS-1$ descriptor.setFileNamePatterns(fFilePatterns); descriptor.setUpdateSimilarDeclarations(fUpdateSimilarElements); descriptor.setMatchStrategy(fRenamingStrategy); return descriptor; } @Override public Change postCreateChange(Change[] participantChanges, IProgressMonitor pm) throws CoreException { if (fQualifiedNameSearchResult != null) { try { return fQualifiedNameSearchResult.getSingleChange(Changes.getModifiedFiles(participantChanges)); } finally { fQualifiedNameSearchResult= null; } } else { return null; } } private boolean willRenameCU() { String name= JavaCore.removeJavaLikeExtension(fType.getCompilationUnit().getElementName()); if (!(Checks.isTopLevel(fType) && name.equals(fType.getElementName()))) return false; if (!checkNewPathValidity().isOK()) return false; if (!Checks.checkCompilationUnitNewName(fType.getCompilationUnit(), getNewElementName()).isOK()) return false; return true; } private void createChanges(IProgressMonitor pm) throws CoreException { try { pm.beginTask("", 12); //$NON-NLS-1$ pm.setTaskName(RefactoringCoreMessages.RenameTypeProcessor_creating_changes); if (fUpdateReferences) addReferenceUpdates(fChangeManager, new SubProgressMonitor(pm, 3)); // Similar names updates have already been added. pm.worked(1); IResource resource= fType.getCompilationUnit().getResource(); // if we have a linked resource then we don't use CU renaming // directly. So we have to update the code by ourselves. if ((resource != null && resource.isLinked()) || !willRenameCU()) { addTypeDeclarationUpdate(fChangeManager); pm.worked(1); addConstructorRenames(fChangeManager); pm.worked(1); } else { pm.worked(2); } if (fUpdateTextualMatches) { pm.subTask(RefactoringCoreMessages.RenameTypeRefactoring_searching_text); TextMatchUpdater.perform(new SubProgressMonitor(pm, 1), RefactoringScopeFactory.create(fType), this, fChangeManager, fReferences); if (fUpdateSimilarElements) addSimilarElementsTextualUpdates(fChangeManager, new SubProgressMonitor(pm, 3)); } } finally { pm.done(); } } private void addTypeDeclarationUpdate(TextChangeManager manager) throws CoreException { String name= RefactoringCoreMessages.RenameTypeRefactoring_update; int typeNameLength= fType.getElementName().length(); ICompilationUnit cu= fType.getCompilationUnit(); TextChangeCompatibility.addTextEdit(manager.get(cu), name, new ReplaceEdit(fType.getNameRange().getOffset(), typeNameLength, getNewElementName())); } private void addConstructorRenames(TextChangeManager manager) throws CoreException { ICompilationUnit cu= fType.getCompilationUnit(); IMethod[] methods= fType.getMethods(); int typeNameLength= fType.getElementName().length(); for (int i= 0; i < methods.length; i++) { if (methods[i].isConstructor()) { /* * constructor declarations cannot be fully qualified so we can use simple replace here * * if (methods[i].getNameRange() == null), then it's a binary file so it's wrong anyway * (checked as a precondition) */ String name= RefactoringCoreMessages.RenameTypeRefactoring_rename_constructor; TextChangeCompatibility.addTextEdit(manager.get(cu), name, new ReplaceEdit(methods[i].getNameRange().getOffset(), typeNameLength, getNewElementName())); } } } private void addReferenceUpdates(TextChangeManager manager, IProgressMonitor pm) { pm.beginTask("", fReferences.length); //$NON-NLS-1$ for (int i= 0; i < fReferences.length; i++) { ICompilationUnit cu= fReferences[i].getCompilationUnit(); if (cu == null) continue; String name= RefactoringCoreMessages.RenameTypeRefactoring_update_reference; SearchMatch[] results= fReferences[i].getSearchResults(); for (int j= 0; j < results.length; j++) { SearchMatch match= results[j]; ReplaceEdit replaceEdit= new ReplaceEdit(match.getOffset(), match.getLength(), getNewElementName()); TextChangeCompatibility.addTextEdit(manager.get(cu), name, replaceEdit, CATEGORY_TYPE_RENAME); } pm.worked(1); } } private void computeQualifiedNameMatches(IProgressMonitor pm) { IPackageFragment fragment= fType.getPackageFragment(); if (fQualifiedNameSearchResult == null) fQualifiedNameSearchResult= new QualifiedNameSearchResult(); QualifiedNameFinder.process(fQualifiedNameSearchResult, fType.getFullyQualifiedName(), fragment.getElementName() + "." + getNewElementName(), //$NON-NLS-1$ fFilePatterns, fType.getJavaProject().getProject(), pm); } private RefactoringStatus initialize(JavaRefactoringArguments extended) { final String handle= extended.getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT); if (handle != null) { final IJavaElement element= JavaRefactoringDescriptorUtil.handleToElement(extended.getProject(), handle, false); if (element == null || !element.exists() || element.getElementType() != IJavaElement.TYPE) return JavaRefactoringDescriptorUtil.createInputFatalStatus(element, getProcessorName(), IJavaRefactorings.RENAME_TYPE); else fType= (IType) element; } else return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT)); final String name= extended.getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME); if (name != null && !"".equals(name)) //$NON-NLS-1$ setNewElementName(name); else return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME)); final String patterns= extended.getAttribute(ATTRIBUTE_PATTERNS); if (patterns != null && !"".equals(patterns)) //$NON-NLS-1$ fFilePatterns= patterns; final String references= extended.getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_REFERENCES); if (references != null) { fUpdateReferences= Boolean.valueOf(references).booleanValue(); } else return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptorUtil.ATTRIBUTE_REFERENCES)); final String matches= extended.getAttribute(ATTRIBUTE_TEXTUAL_MATCHES); if (matches != null) { fUpdateTextualMatches= Boolean.valueOf(matches).booleanValue(); } else return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_TEXTUAL_MATCHES)); final String qualified= extended.getAttribute(ATTRIBUTE_QUALIFIED); if (qualified != null) { fUpdateQualifiedNames= Boolean.valueOf(qualified).booleanValue(); } else return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_QUALIFIED)); final String similarDeclarations= extended.getAttribute(ATTRIBUTE_SIMILAR_DECLARATIONS); if (similarDeclarations != null) fUpdateSimilarElements= Boolean.valueOf(similarDeclarations).booleanValue(); else return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_SIMILAR_DECLARATIONS)); final String similarDeclarationsMatchingStrategy= extended.getAttribute(ATTRIBUTE_MATCHING_STRATEGY); if (similarDeclarationsMatchingStrategy != null) { try { fRenamingStrategy= Integer.valueOf(similarDeclarationsMatchingStrategy).intValue(); } catch (NumberFormatException e) { return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_illegal_argument, new String[] { similarDeclarationsMatchingStrategy, ATTRIBUTE_QUALIFIED })); } } else return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_MATCHING_STRATEGY)); return new RefactoringStatus(); } // --------- Similar names /** * Creates and initializes the refactoring processors for similarly named elements * * @param progressMonitor progress monitor * @param context context * @return status * @throws CoreException should not happen */ private RefactoringStatus initializeSimilarElementsRenameProcessors(IProgressMonitor progressMonitor, CheckConditionsContext context) throws CoreException { Assert.isNotNull(fPreloadedElementToName); Assert.isNotNull(fPreloadedElementToSelection); final RefactoringStatus status= new RefactoringStatus(); final Set<IMethod> handledTopLevelMethods= new HashSet<IMethod>(); final Set<Warning> warnings= new HashSet<Warning>(); final List<RefactoringProcessor> processors= new ArrayList<RefactoringProcessor>(); fFinalSimilarElementToName= new HashMap<IJavaElement, String>(); CompilationUnit currentResolvedCU= null; ICompilationUnit currentCU= null; int current= 0; final int max= fPreloadedElementToName.size(); progressMonitor.beginTask("", max * 3); //$NON-NLS-1$ progressMonitor.setTaskName(RefactoringCoreMessages.RenameTypeProcessor_checking_similarly_named_declarations_refactoring_conditions); for (Iterator<IJavaElement> iter= fPreloadedElementToName.keySet().iterator(); iter.hasNext();) { final IJavaElement element= iter.next(); current++; progressMonitor.worked(3); // not selected? -> skip if (!(fPreloadedElementToSelection.get(element)).booleanValue()) continue; // already registered? (may happen with overridden methods) -> skip if (fFinalSimilarElementToName.containsKey(element)) continue; // CompilationUnit changed? (note: fPreloadedElementToName is sorted by CompilationUnit) ICompilationUnit newCU= (ICompilationUnit) element.getAncestor(IJavaElement.COMPILATION_UNIT); if (!newCU.equals(currentCU)) { checkCUCompleteConditions(status, currentResolvedCU, currentCU, processors); if (status.hasFatalError()) return status; // reset values currentResolvedCU= null; currentCU= newCU; processors.clear(); } final String newName= fPreloadedElementToName.get(element); RefactoringProcessor processor= null; if (element instanceof ILocalVariable) { final ILocalVariable currentLocal= (ILocalVariable) element; if (currentResolvedCU == null) currentResolvedCU= new RefactoringASTParser(ASTProvider.SHARED_AST_LEVEL).parse(currentCU, true); processor= createLocalRenameProcessor(currentLocal, newName, currentResolvedCU); // don't check for conflicting rename => is done by #checkCUCompleteConditions(). if (status.hasFatalError()) return status; fFinalSimilarElementToName.put(currentLocal, newName); } if (element instanceof IField) { final IField currentField= (IField) element; processor= createFieldRenameProcessor(currentField, newName); status.merge(checkForConflictingRename(currentField, newName)); if (status.hasFatalError()) return status; fFinalSimilarElementToName.put(currentField, newName); } if (element instanceof IMethod) { IMethod currentMethod= (IMethod) element; if (MethodChecks.isVirtual(currentMethod)) { final IType declaringType= currentMethod.getDeclaringType(); ITypeHierarchy hierarchy= null; if (!declaringType.isInterface()) hierarchy= declaringType.newTypeHierarchy(new NullProgressMonitor()); final IMethod topmost= MethodChecks.getTopmostMethod(currentMethod, hierarchy, new NullProgressMonitor()); if (topmost != null) currentMethod= topmost; if (handledTopLevelMethods.contains(currentMethod)) continue; handledTopLevelMethods.add(currentMethod); final IMethod[] ripples= RippleMethodFinder2.getRelatedMethods(currentMethod, new NullProgressMonitor(), null); if (checkForWarnings(warnings, newName, ripples)) continue; status.merge(checkForConflictingRename(ripples, newName)); if (status.hasFatalError()) return status; processor= createVirtualMethodRenameProcessor(currentMethod, newName, ripples, hierarchy); fFinalSimilarElementToName.put(currentMethod, newName); for (int i= 0; i < ripples.length; i++) { fFinalSimilarElementToName.put(ripples[i], newName); } } else { status.merge(checkForConflictingRename(new IMethod[] { currentMethod }, newName)); if (status.hasFatalError()) break; fFinalSimilarElementToName.put(currentMethod, newName); processor= createNonVirtualMethodRenameProcessor(currentMethod, newName); } } progressMonitor.subTask(Messages.format(RefactoringCoreMessages.RenameTypeProcessor_progress_current_total, new Object[] { String.valueOf(current), String.valueOf(max) })); status.merge(processor.checkInitialConditions(new NoOverrideProgressMonitor(progressMonitor, 1))); if (status.hasFatalError()) return status; status.merge(processor.checkFinalConditions(new NoOverrideProgressMonitor(progressMonitor, 1), context)); if (status.hasFatalError()) return status; processors.add(processor); progressMonitor.worked(1); if (progressMonitor.isCanceled()) throw new OperationCanceledException(); } // check last CU checkCUCompleteConditions(status, currentResolvedCU, currentCU, processors); status.merge(addWarnings(warnings)); progressMonitor.done(); return status; } private void checkCUCompleteConditions(final RefactoringStatus status, CompilationUnit currentResolvedCU, ICompilationUnit currentCU, List<RefactoringProcessor> processors) throws CoreException { // check local variable conditions List<RefactoringProcessor> locals= getProcessorsOfType(processors, RenameLocalVariableProcessor.class); if (!locals.isEmpty()) { RenameAnalyzeUtil.LocalAnalyzePackage[] analyzePackages= new RenameAnalyzeUtil.LocalAnalyzePackage[locals.size()]; TextChangeManager manager= new TextChangeManager(); int current= 0; TextChange textChange= manager.get(currentCU); textChange.setKeepPreviewEdits(true); for (Iterator<RefactoringProcessor> iterator= locals.iterator(); iterator.hasNext();) { RenameLocalVariableProcessor localProcessor= (RenameLocalVariableProcessor) iterator.next(); RenameAnalyzeUtil.LocalAnalyzePackage analyzePackage= localProcessor.getLocalAnalyzePackage(); analyzePackages[current]= analyzePackage; for (int i= 0; i < analyzePackage.fOccurenceEdits.length; i++) { TextChangeCompatibility.addTextEdit(textChange, "", analyzePackage.fOccurenceEdits[i], GroupCategorySet.NONE); //$NON-NLS-1$ } current++; } status.merge(RenameAnalyzeUtil.analyzeLocalRenames(analyzePackages, textChange, currentResolvedCU, false)); } /* * There is room for performance improvement here: One could move * shadowing analyzes out of the field and method processors and perform * it here, thus saving on working copy creation. Drawback is increased * heap consumption. */ } private List<RefactoringProcessor> getProcessorsOfType(List<RefactoringProcessor> processors, Class<RenameLocalVariableProcessor> type) { List<RefactoringProcessor> tmp= new ArrayList<RefactoringProcessor>(); for (Iterator<RefactoringProcessor> iter= processors.iterator(); iter.hasNext();) { RefactoringProcessor element= iter.next(); if (element.getClass().equals(type)) tmp.add(element); } return tmp; } // ------------------ Error checking ------------- /** * Checks whether one of the given methods, which will all be renamed to "newName", shares a * type with another already registered method which is renamed to the same new name and shares * the same parameters. * * @param methods methods to check * @param newName the new name * @return status * * @see #checkForConflictingRename(IField, String) */ private RefactoringStatus checkForConflictingRename(IMethod[] methods, String newName) { RefactoringStatus status= new RefactoringStatus(); for (Iterator<IJavaElement> iter= fFinalSimilarElementToName.keySet().iterator(); iter.hasNext();) { IJavaElement element= iter.next(); if (element instanceof IMethod) { IMethod alreadyRegisteredMethod= (IMethod) element; String alreadyRegisteredMethodName= fFinalSimilarElementToName.get(element); for (int i= 0; i < methods.length; i++) { IMethod method2= methods[i]; if ((alreadyRegisteredMethodName.equals(newName)) && (method2.getDeclaringType().equals(alreadyRegisteredMethod.getDeclaringType())) && (sameParams(alreadyRegisteredMethod, method2))) { String message= Messages.format(RefactoringCoreMessages.RenameTypeProcessor_cannot_rename_methods_same_new_name, new String[] { BasicElementLabels.getJavaElementName(alreadyRegisteredMethod.getElementName()), BasicElementLabels.getJavaElementName(method2.getElementName()), BasicElementLabels.getJavaElementName(alreadyRegisteredMethod.getDeclaringType().getFullyQualifiedName('.')), BasicElementLabels.getJavaElementName(newName) }); status.addFatalError(message); return status; } } } } return status; } private static boolean sameParams(IMethod method, IMethod method2) { if (method.getNumberOfParameters() != method2.getNumberOfParameters()) return false; String[] params= method.getParameterTypes(); String[] params2= method2.getParameterTypes(); for (int i= 0; i < params.length; i++) { String t1= Signature.getSimpleName(Signature.toString(params[i])); String t2= Signature.getSimpleName(Signature.toString(params2[i])); if (!t1.equals(t2)) { return false; } } return true; } /** * If suffix matching is enabled, the refactoring may suggest two fields to have the same name * which reside in the same type. Same thing may also happen if the user makes poor choices for * the field names. * * Consider: FooBarThing fFooBarThing; FooBarThing fBarThing; * * Rename "FooBarThing" to "DifferentHunk". Suggestion for both fields is "fDifferentHunk" (and * rightly so). * * @param currentField field * @param newName new name * @return status */ private RefactoringStatus checkForConflictingRename(IField currentField, String newName) { RefactoringStatus status= new RefactoringStatus(); for (Iterator<IJavaElement> iter= fFinalSimilarElementToName.keySet().iterator(); iter.hasNext();) { IJavaElement element= iter.next(); if (element instanceof IField) { IField alreadyRegisteredField= (IField) element; String alreadyRegisteredFieldName= fFinalSimilarElementToName.get(element); if (alreadyRegisteredFieldName.equals(newName)) { if (alreadyRegisteredField.getDeclaringType().equals(currentField.getDeclaringType())) { String message= Messages.format(RefactoringCoreMessages.RenameTypeProcessor_cannot_rename_fields_same_new_name, new String[] { BasicElementLabels.getJavaElementName(alreadyRegisteredField.getElementName()), BasicElementLabels.getJavaElementName(currentField.getElementName()), BasicElementLabels.getJavaElementName(alreadyRegisteredField.getDeclaringType().getFullyQualifiedName('.')), BasicElementLabels.getJavaElementName(newName) }); status.addFatalError(message); return status; } } } } return status; } private RefactoringStatus addWarnings(final Set<Warning> warnings) { RefactoringStatus status= new RefactoringStatus(); // Remove deleted ripple methods from user selection and add warnings for (Iterator<Warning> iter= warnings.iterator(); iter.hasNext();) { final Warning warning= iter.next(); final IMethod[] elements= warning.getRipple(); if (warning.isSelectionWarning()) { String message= Messages.format(RefactoringCoreMessages.RenameTypeProcessor_deselected_method_is_overridden, new String[] { JavaElementLabels.getElementLabel(elements[0], JavaElementLabels.ALL_DEFAULT), JavaElementLabels.getElementLabel(elements[0].getDeclaringType(), JavaElementLabels.ALL_DEFAULT) }); status.addWarning(message); } if (warning.isNameWarning()) { String message= Messages.format( RefactoringCoreMessages.RenameTypeProcessor_renamed_method_is_overridden, new String[] { JavaElementLabels.getElementLabel(elements[0], JavaElementLabels.ALL_DEFAULT), JavaElementLabels.getElementLabel(elements[0].getDeclaringType(), JavaElementLabels.ALL_DEFAULT) }); status.addWarning(message); } for (int i= 0; i < elements.length; i++) fPreloadedElementToSelection.put(elements[i], Boolean.FALSE); } return status; } /* * If one of the methods of this ripple was deselected or renamed by * the user, deselect the whole chain and add warnings. */ private boolean checkForWarnings(final Set<Warning> warnings, final String newName, final IMethod[] ripples) { boolean addSelectionWarning= false; boolean addNameWarning= false; for (int i= 0; i < ripples.length; i++) { String newNameOfRipple= fPreloadedElementToName.get(ripples[i]); Boolean selected= fPreloadedElementToSelection.get(ripples[i]); // selected may be null here due to supermethods like // setSomeClass(Object class) (subsignature match) // Don't add a warning. if (selected == null) continue; if (!selected.booleanValue()) addSelectionWarning= true; if (!newName.equals(newNameOfRipple)) addNameWarning= true; } if (addSelectionWarning || addNameWarning) warnings.add(new Warning(ripples, addSelectionWarning, addNameWarning)); return (addSelectionWarning || addNameWarning); } private class Warning { private IMethod[] fRipple; private boolean fSelectionWarning; private boolean fNameWarning; public Warning(IMethod[] ripple, boolean isSelectionWarning, boolean isNameWarning) { fRipple= ripple; fSelectionWarning= isSelectionWarning; fNameWarning= isNameWarning; } public boolean isNameWarning() { return fNameWarning; } public IMethod[] getRipple() { return fRipple; } public boolean isSelectionWarning() { return fSelectionWarning; } } // ----------------- Processor creation -------- private RenameMethodProcessor createVirtualMethodRenameProcessor(IMethod currentMethod, String newMethodName, IMethod[] ripples, ITypeHierarchy hierarchy) { RenameMethodProcessor processor= new RenameVirtualMethodProcessor(currentMethod, ripples, fChangeManager, hierarchy, CATEGORY_METHOD_RENAME); initMethodProcessor(processor, newMethodName); return processor; } private RenameMethodProcessor createNonVirtualMethodRenameProcessor(IMethod currentMethod, String newMethodName) { RenameMethodProcessor processor= new RenameNonVirtualMethodProcessor(currentMethod, fChangeManager, CATEGORY_METHOD_RENAME); initMethodProcessor(processor, newMethodName); return processor; } private void initMethodProcessor(RenameMethodProcessor processor, String newMethodName) { processor.setNewElementName(newMethodName); processor.setUpdateReferences(getUpdateReferences()); } private RenameFieldProcessor createFieldRenameProcessor(final IField field, final String newName) { final RenameFieldProcessor processor= new RenameFieldProcessor(field, fChangeManager, CATEGORY_FIELD_RENAME); processor.setNewElementName(newName); processor.setRenameGetter(false); processor.setRenameSetter(false); processor.setUpdateReferences(getUpdateReferences()); processor.setUpdateTextualMatches(false); return processor; } private RenameLocalVariableProcessor createLocalRenameProcessor(final ILocalVariable local, final String newName, final CompilationUnit compilationUnit) { final RenameLocalVariableProcessor processor= new RenameLocalVariableProcessor(local, fChangeManager, compilationUnit, CATEGORY_LOCAL_RENAME); processor.setNewElementName(newName); processor.setUpdateReferences(getUpdateReferences()); return processor; } // ----------- Edit creation ----------- /** * Updates textual matches for fields. * * Strategy for matching text matches: Match and replace all fully qualified field names, but * non-qualified field names only iff there are no fields which have the same original, but a * different new name. Don't add java references; duplicate edits may be created but do not * matter. * * @param manager text change manager * @param monitor progress monitor * @throws CoreException if updating failed */ private void addSimilarElementsTextualUpdates(TextChangeManager manager, IProgressMonitor monitor) throws CoreException { final Map<String, String> simpleNames= new HashMap<String, String>(); final List<String> forbiddenSimpleNames= new ArrayList<String>(); for (Iterator<IJavaElement> iter= fFinalSimilarElementToName.keySet().iterator(); iter.hasNext();) { final IJavaElement element= iter.next(); if (element instanceof IField) { if (forbiddenSimpleNames.contains(element.getElementName())) continue; final String registeredNewName= simpleNames.get(element.getElementName()); final String newNameToCheck= fFinalSimilarElementToName.get(element); if (registeredNewName == null) simpleNames.put(element.getElementName(), newNameToCheck); else if (!registeredNewName.equals(newNameToCheck)) forbiddenSimpleNames.add(element.getElementName()); } } for (Iterator<IJavaElement> iter= fFinalSimilarElementToName.keySet().iterator(); iter.hasNext();) { final IJavaElement element= iter.next(); if (element instanceof IField) { final IField field= (IField) element; final String newName= fFinalSimilarElementToName.get(field); TextMatchUpdater.perform(monitor, RefactoringScopeFactory.create(field), field.getElementName(), field.getDeclaringType().getFullyQualifiedName(), newName, manager, new SearchResultGroup[0], forbiddenSimpleNames.contains(field.getElementName())); } } } // ------ UI interaction /** * @return the map of similarly named elements (IJavaElement -> String with new name) This map * is live. Callers may change the new names of the elements; they may not change the * key set. */ public Map<IJavaElement, String> getSimilarElementsToNewNames() { return fPreloadedElementToName; } /** * @return the map of similarly named elements (IJavaElement -> Boolean if selected) This map is * live. Callers may change the selection status of the elements; they may not change * the key set. */ public Map<IJavaElement, Boolean> getSimilarElementsToSelection() { return fPreloadedElementToSelection; } /** * Resets the element maps back to the original status. This affects the maps returned in * {@link #getSimilarElementsToNewNames() } and {@link #getSimilarElementsToSelection() }. All new * names are reset to the calculated ones and every element gets selected. */ public void resetSelectedSimilarElements() { Assert.isNotNull(fPreloadedElementToName); for (Iterator<IJavaElement> iter= fPreloadedElementToNameDefault.keySet().iterator(); iter.hasNext();) { final IJavaElement element= iter.next(); fPreloadedElementToName.put(element, fPreloadedElementToNameDefault.get(element)); fPreloadedElementToSelection.put(element, Boolean.TRUE); } } /** * @return true iff the "update similarly named elements" flag is set AND the search yielded * some elements to be renamed. */ public boolean hasSimilarElementsToRename() { if (!fUpdateSimilarElements) return false; if (fPreloadedElementToName == null) return false; if (fPreloadedElementToName.size() == 0) return false; return true; } ///////////////// //CODINGSPECTATOR ///////////////// public JavaRefactoringDescriptor getOriginalRefactoringDescriptor() { return createRefactoringDescriptor(); } public String getDescriptorID() { return IJavaRefactorings.RENAME_TYPE; } }